home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Think Class Libraries / CDecimalText 1.0 / CDecimalText.cp next >
Encoding:
Text File  |  1994-11-30  |  11.2 KB  |  417 lines  |  [TEXT/KAHL]

  1. /******************************************************************************
  2.  CDecimalText.c
  3.  
  4.         A subclass of CDialogText that accepts only decimal entries. Any
  5.         characters are accepted as the user types, and the entry is checked
  6.         at validation. Validation occurs when the user tabs out of the field
  7.         or tries to confirm the dialog.
  8.         
  9.         CDecimalText can constrain values to a certain number of decimal
  10.         places. If 0 decimal places are allowed, then users may enter
  11.         only integers. If more than 0 decimal places are allowed, then
  12.         users may enter numbers of only so many decimal places. To allow
  13.         entry of any number, allow 50 decimal places, for example.
  14.         
  15.         SANE is used to to convert the text to a number because it provides
  16.         Script Manager support.
  17.         
  18.         As a subclass of CDialogText, CDecimalText fields may or not be required
  19.         and have a maximum allowed length. Required fields are considered invalid
  20.         if left empty. Additionally CDecimalText fields may have a default value,
  21.         which is the value returned if the text is empty. They also have a
  22.         minimum and maximum allowed value.
  23.         
  24.         To use this class, you must include the SANE library in your project.
  25.         
  26.     SUPERCLASS = CDialogText
  27.     
  28.     Copyright © 1993 Michael Abramowicz. All rights reserved.
  29.     
  30.  
  31.  ******************************************************************************/
  32.  
  33. #include "CDecimalText.h"
  34. #include <SANE.h>
  35. #include "Global.h"
  36. #include "TBUtilities.h"
  37. #include <Packages.h>
  38.  
  39. /******************************************************************************
  40.  IDecimalText
  41.  
  42.      Initialize the CDecimalText object. The default constraints are:
  43.      no max length, required, range (MINLONG, MAXLONG), showRangeOnErr
  44.      is FALSE, and fields need not be constrained to integer values.
  45. ******************************************************************************/
  46.  
  47. void CDecimalText::IDecimalText( CView *anEnclosure, CView *aSupervisor,
  48.                     short aWidth, short aHeight,
  49.                     short aHEncl, short aVEncl,
  50.                     SizingOption aHSizing, SizingOption aVSizing,
  51.                     short aLineWidth)
  52. {
  53.     CDialogText::IDialogText( anEnclosure, aSupervisor, aWidth, aHeight,
  54.                     aHEncl, aVEncl, aHSizing, aVSizing, aLineWidth);
  55.                     
  56.     minValue = MINLONG;
  57.     maxValue = MAXLONG;
  58.     defaultValue = 0;
  59.     isRequired = TRUE;
  60.     decimalPlaces = 50;            /* Virtually no limit. */
  61. }
  62.  
  63. /******************************************************************************
  64.  IViewTemp
  65.  
  66.      Initialize the CDecimalText object from a resource template.
  67. ******************************************************************************/
  68.  
  69. void CDecimalText::IViewTemp(CView *anEnclosure, CBureaucrat *aSupervisor,
  70.                             Ptr viewData)
  71. {
  72.     tDecimalTextTempP data = (tDecimalTextTempP) viewData;
  73.     
  74.     inherited::IViewTemp( anEnclosure, aSupervisor, viewData);
  75.     
  76.     minValue = data->minValue;
  77.     maxValue = data->maxValue;
  78.     
  79.     // As a special case, we interpret that
  80.     // if minValue == maxValue, the intent is to allow all decimals
  81.     
  82.     if (minValue == maxValue)
  83.     {
  84.         minValue = MINLONG;
  85.         maxValue = MAXLONG;
  86.     }
  87.     
  88.     defaultValue = data->defaultValue;
  89.     
  90.     decimalPlaces = data->decimalPlaces;
  91. }
  92.  
  93. /******************************************************************************
  94.  SpecifyDecimalPlaces
  95.  
  96.      Specify how many decimal places should be allowed for the field.
  97.      If more than the requisite number are already in this field, then 
  98.      an error is displayed if doValidate is TRUE.
  99. ******************************************************************************/
  100. void CDecimalText::SpecifyDecimalPlaces( short numPlaces, Boolean doValidate)
  101. {
  102.     decimalPlaces = numPlaces;
  103.     if (doValidate)
  104.         if (!Validate()) {
  105.             SelectAll(TRUE);
  106.             BecomeGopher(TRUE);
  107.         }
  108. }
  109.  
  110. /******************************************************************************
  111.  GetDecimalPlaces
  112.  
  113.      Return how many decimal places are allowed for the field.
  114. ******************************************************************************/
  115. short CDecimalText::GetDecimalPlaces(void)
  116. {
  117.     return decimalPlaces;
  118. }
  119.  
  120. /******************************************************************************
  121.  SpecifyRange
  122.  
  123.      Specify the allowed range of values. If the range is MINLONG-MAXLONG,
  124.      then showRangeOnErr is turned off. Note that the allowable range must
  125.      be bound by integers.
  126. ******************************************************************************/
  127.  
  128. void CDecimalText::SpecifyRange( long aMinimum, long aMaximum)
  129. {
  130.     ASSERT( aMaximum > aMinimum);
  131.     
  132.     minValue = aMinimum;
  133.     maxValue = aMaximum;
  134.     
  135. }
  136.  
  137. /******************************************************************************
  138.  SpecifyDefaultValue
  139.  
  140.      Specifying a default value implies that the text is valid if empty, 
  141.      therefore isRequired is set FALSE. Note that the default value must
  142.      be an integer.
  143. ******************************************************************************/
  144.  
  145. void CDecimalText::SpecifyDefaultValue( long aDefaultValue)
  146. {
  147.     defaultValue = aDefaultValue;
  148.     isRequired = FALSE;
  149. }
  150.  
  151. /******************************************************************************
  152.  SetDecValue
  153.  
  154.      Set the text to aValue.
  155. ******************************************************************************/
  156.  
  157. void CDecimalText::SetDecValue( double aValue)
  158. {
  159.     Str255        numStr, oldText;
  160.     extended    x;
  161.     decform        dec;
  162.     Boolean        trailingZeros = FALSE;
  163.     short        index;
  164.     long double    tmp;
  165.     
  166.     GetTextString( oldText);
  167.     
  168.     tmp = aValue;
  169.     
  170. #ifdef __SC__
  171.     #if mc68881
  172.         x96tox80(&tmp, &x);
  173.     #else
  174.         x = tmp;
  175.     #endif
  176. #else
  177.     #if __option(native_fp) && !__option(mc68881)
  178.         x = tmp;
  179.     #else
  180.         x96tox80( &tmp, &x);
  181.     #endif
  182. #endif
  183.  
  184.     dec.style = FIXEDDECIMAL;
  185.     dec.digits = decimalPlaces;
  186.     
  187.     num2str( &dec, x, numStr);
  188.  
  189.         /* check for trailing 0’s */
  190.     if (numStr[numStr[0]] == '0')
  191.         for (index = 0; ++index < numStr[0];) {
  192.             if (numStr[index] == '.') {
  193.                 trailingZeros = TRUE;
  194.                 index = 256;
  195.             }
  196.         }
  197.         
  198.         /* eliminate trailing 0’s if necessary */
  199.     if (trailingZeros) {
  200.                 /* eliminate zeros */
  201.         while ((numStr[numStr[0]] == '0') && (numStr[0] != 1)) 
  202.             numStr[0]--;        
  203.                 /* eliminate period from end if applicable */
  204.         while (numStr[numStr[0]] == '.')
  205.             numStr[0]--;
  206.     }
  207.     
  208.     SetTextString( numStr);
  209.     
  210.     if (!Validate())
  211.         SetTextString( oldText);
  212. }
  213.  
  214. /******************************************************************************
  215.  GetDecValue
  216.  
  217.      Get the current value. If the text is empty or the field is invalid, 
  218.      the default value is returned.
  219. ******************************************************************************/
  220.  
  221. double CDecimalText::GetDecValue( void)
  222. {
  223.     double    value;
  224.     Validity valid;
  225.     
  226.     ConvertToDecimal( &value, &valid);
  227.             
  228.     return value;
  229. }
  230.  
  231. /******************************************************************************
  232.  ReportInvalidText {OVERRIDE}
  233.  
  234.      Scroll so that the beginning of the selection is visible before the
  235.      entire selection is selected.
  236. ******************************************************************************/
  237. void CDecimalText::ReportInvalidText( short strIndex)
  238. {
  239.     SetSelection(0,0,TRUE);
  240.     ScrollToSelection();
  241.     inherited::ReportInvalidText(strIndex);
  242. }
  243.  
  244. /******************************************************************************
  245.  Validate {OVERRIDE}
  246.  
  247.      Ensure that all the constraints have been met. If validation fails,
  248.      the ReportInvalidText method is called to show an alert.
  249. ******************************************************************************/
  250. Boolean CDecimalText::Validate( void)
  251. {
  252.     double value;
  253.     Validity valid;
  254.     Str31    minText, maxText, placesText;
  255.     
  256.     if (inherited::Validate())
  257.     {
  258.         ConvertToDecimal( &value, &valid);
  259.  
  260.         switch (valid) {
  261.         
  262.             case kNonNumeric:
  263.                 ReportInvalidText( validateNonNumeric);
  264.                 break;
  265.                 
  266.             case kNonIntegral:
  267.                 ReportInvalidText( validateNonIntegral);
  268.                 break;
  269.                 
  270.             case kTooManyDecimalPlaces:
  271.                 NumToString( (long) decimalPlaces, placesText);
  272.                 ParamText( NULL, placesText, NULL, NULL);
  273.                 ReportInvalidText( validateDecPlaces);
  274.                 break;
  275.                 
  276.             case kOutOfRange:
  277.                 NumToString( minValue, minText);
  278.                 NumToString( maxValue, maxText);
  279.                 ParamText( NULL, minText, maxText, NULL);
  280.                 ReportInvalidText( validateOutOfRange);
  281.                 
  282.             default: 
  283.                 break;            /* Entry is valid */
  284.         };
  285.         return (valid == kValid);
  286.     }
  287.     else
  288.         return FALSE;
  289. }
  290.  
  291. /******************************************************************************
  292.  ConvertToDecimal
  293.  
  294.      Protected method to convert the text to a double. Returns kValid if
  295.      the text was a valid decimal entry within valid limits. If there is 
  296.      an invalid entry, then return the appropriate validity code and the
  297.      default value.
  298. ******************************************************************************/
  299.  
  300. void CDecimalText::ConvertToDecimal( double *decValue, Validity *valid)
  301. {
  302.     Str255    str;
  303.     short    index, length;
  304.     decimal dec;
  305.     Boolean    validPrefix = 0;
  306.     extended  x;
  307.     double  minLng, maxLng;
  308.     double val;
  309.     
  310.     *valid = kValid;
  311.     
  312.     GetTextString( str);
  313.         
  314.     length = Length(str);
  315.     if (length == 0)
  316.     {
  317.         if (isRequired)
  318.             *valid = kOtherReason;
  319.         *decValue = defaultValue;
  320.     }
  321.     else
  322.     {        
  323.         index = 1;
  324.         
  325.         
  326.         str2dec( str, &index, &dec, &validPrefix);
  327.         
  328.         // if validPrefix is TRUE and index is past the last char,
  329.         // then the string is a valid numeric string. If the
  330.         // exp field is non-negative, then it is a valid integer
  331.         
  332.         if (!(validPrefix && (index > length)))
  333.             *valid = kNonNumeric;
  334.         else {
  335.             if ((decimalPlaces == 0) && (dec.exp < 0))
  336.                 *valid = kNonIntegral;
  337.             else if ( -dec.exp > decimalPlaces)
  338.                 *valid = kTooManyDecimalPlaces;
  339.         }
  340.         if (*valid == kValid)
  341.         {
  342.             x = dec2num( &dec);
  343.             
  344. #ifdef __SC__
  345.     #if mc68881
  346.         x80tox96( &x,  &val);
  347.     #else
  348.         val = x;
  349.     #endif
  350. #else
  351.     #if __option(native_fp) && !__option(mc68881)
  352.             val = x;
  353.     #else
  354.             x80tox96( &x,  &val);
  355.     #endif
  356. #endif
  357.             minLng = (long) MINLONG;
  358.             maxLng = MAXLONG;
  359.             if ((val >= minLng) && (val <= maxLng) && (val >= minValue) && (val <= maxValue))
  360.                 *decValue = val;
  361.             else
  362.                 *valid = kOutOfRange;
  363.         }
  364.     }
  365.     if (*valid != kValid)
  366.         *decValue = (double) defaultValue;
  367. }
  368.  
  369. /******************************************************************************
  370.  DoCommand
  371.  
  372.      When a cmdPaste is received, then round to the nearest permissible
  373.      decimal, unless no decimals at all are permitted.
  374. ******************************************************************************/
  375. void CDecimalText::DoCommand(long theCommand)
  376. {
  377.     CTextEditTask    *editTask = NULL;
  378.     long            selStart, selEnd;
  379.     short            saveDecimalPlaces,
  380.                     tempDecimalPlaces = 50;
  381.     double            decimalValue;
  382.     Validity        valid;
  383.     
  384.     if (theCommand == cmdPaste) {
  385.         GetSelection(&selStart, &selEnd);
  386.         if (selStart == 0 && selEnd == GetLength() && decimalPlaces != 0) {
  387.                 /* Temporarily disable decimal place checking and get the
  388.                 entire decimal value. */    
  389.             saveDecimalPlaces = GetDecimalPlaces();
  390.             SpecifyDecimalPlaces(tempDecimalPlaces, FALSE);
  391.             
  392.                 /* Now paste in the text. */
  393.             inherited::DoCommand(theCommand);
  394.             
  395.                 /* Get the decimal value. */
  396.             ConvertToDecimal(&decimalValue, &valid);
  397.             SpecifyDecimalPlaces(saveDecimalPlaces, FALSE);
  398.             
  399.                 /* Set to the decimal value, truncating, and scroll so
  400.                 that the beginning of the pasted in text is visible. */
  401.             if (valid == kValid) {
  402.                 SetDecValue(decimalValue);
  403.                 GetSelection(&selStart, &selEnd);
  404.                 SetSelection(0,0, FALSE);
  405.                 ScrollToSelection();
  406.                 SetSelection(selStart, selEnd, TRUE);
  407.             };
  408.         }
  409.         else {        /* Handle insertion paste the regular way. */
  410.             inherited::DoCommand(theCommand);
  411.         }
  412.         BroadcastChange(dialogTextChanged, NULL);
  413.     }
  414.     else
  415.         inherited::DoCommand(theCommand);
  416.     
  417. }